﻿// Copyright 2012 Stuart Turner
// Licensed under the Apache License, Version 2.0 [http://www.apache.org/licenses/LICENSE-2.0]

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using Complete.Data.Internal;
using Complete.Data.Linq;
using Complete.Data.Support;

namespace Complete.Data
{
	public class CompleteQueryProvider : QueryProvider, IDisposable
	{
		private CompleteContext _Context;

		public CompleteQueryProvider(CompleteContext context)
		{
			_Connection = null;
			_Context = context;
		}

		public CompleteQueryProvider(DbConnection conn, CompleteContext context)
		{
			_Connection = conn;
			_Context = context;
		}

		private DbConnection _Connection;

		public override object Execute(Expression expression)
		{
			if (_Connection == null)
				throw new InvalidOperationException("DbConnection must be provided in order to execute results");

			var fe = TranslateExpression(expression);

			switch (fe.ResultType)
			{
				case ResultType.Scalar:
					return null;

				case ResultType.SingleResultSet:
					return ExecuteResult(fe);

				case ResultType.MultipleResultSets:
					return null;

				default:
					throw new NotSupportedException("Should never get here.");
			}
		}

		private DbCommand PrepareCommand(FormattedExpression fe)
		{
			switch (_Connection.State)
			{
				case System.Data.ConnectionState.Closed:
					_Connection.Open();
					goto case System.Data.ConnectionState.Open;

				case System.Data.ConnectionState.Open:
					var comm = _Connection.CreateCommand();

					comm.CommandText = fe.CommandText;
					foreach (var p in fe.Parameters)
						AddParameter(p, comm);

					return comm;

				default:
					throw new NotImplementedException("Not sure what to do here yet...");
			}
		}

		private void AddParameter(DbParameter parameter, DbCommand command)
		{
			var newParam = command.CreateParameter();
			newParam.Value = parameter.Value;
			newParam.Direction = parameter.Direction;
			newParam.Size = parameter.Size;
			newParam.SourceColumn = parameter.SourceColumn;
			newParam.SourceVersion = parameter.SourceVersion;
			newParam.SourceColumnNullMapping = parameter.SourceColumnNullMapping;
			newParam.IsNullable = parameter.IsNullable;
			newParam.ParameterName = parameter.ParameterName;

			command.Parameters.Add(newParam);
		}

		private object ExecuteResult(FormattedExpression fe)
		{
			using (var command = PrepareCommand(fe))
			using (var reader = command.ExecuteReader())
			{
				return QueryMaterializer.Materialize(reader, fe.Expression.Projection, _Context);
			}
		}

		private object ExecuteScalar(FormattedExpression te)
		{
			return null;
		}

		private FormattedExpression TranslateExpression(Expression expression)
		{
			expression = QueryBinder.Bind(expression, _Context.NameMapper);
			// expression = QueryOptimizer.Optimize(expression);
			return QueryFormatter.Format(expression);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (_Connection != null)
					_Connection.Dispose();
			}
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
	}
}
